/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.xuexiang.xui_lib.component.picker;


import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.utils.Color;
import ohos.agp.utils.Rect;
import ohos.app.Context;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.multimodalinput.event.TouchEvent;

import com.xuexiang.xui_lib.ResourceTable;
import com.xuexiang.xui_lib.util.AttrValue;
import com.xuexiang.xui_lib.util.SizeUtils;

import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;

/**
 * 双向滑块选择器
 *
 * @since 2020-01-04
 */
public class XRangeSlider extends Component implements Component.EstimateSizeListener,
    Component.DrawTask, Component.TouchEventListener {

    public interface OnRangeSliderListener {
        /**
         * max value changed callback
         *
         * @param slider XRangeSlider
         * @param maxValue 最大值
         */
        void onMaxChanged(XRangeSlider slider, int maxValue);

        /**
         * min value changed callback
         *
         * @param slider 样式
         * @param minValue 最小值
         */
        void onMinChanged(XRangeSlider slider, int minValue);

    }

    private  int DEFAULT_TOUCH_TARGET_SIZE;
    private  int DEFAULT_TEXT_MIN_SPACE;
    private static final int DEFAULT_MAX = 100;
    /**
     * 刻度的宽度参数
     */
    private static final float DEFAULT_BIG_SCALE_WITH = 1.7f;
    private static final float DEFAULT_MIDDLE_SCALE_WITH = 1.2f;
    private static final float DEFAULT_SMALL_SCALE_WITH = 1.0f;

    private final Paint mPaint = new Paint();
    private int mLineStartX;
    private int mLineEndX;
    private int mLineLength;
    private int mMinPosition = 0;
    private int mMaxPosition = 0;
    private int mRange;
    private float mConvertFactor;
    private int mMiddleY = 0;
    private boolean mLastTouchedMin;
    private boolean mIsTouchingMin;
    private int mStartingMin = -1;
    private int mStartingMax = -1;
    private boolean mIsFirstInit = true;
    private boolean mIsTouching = false;
    //add
    private Rect mMinTextRect = new Rect();
    private Rect mMaxTextRect = new Rect();
    private Rect mRulerTextRect = new Rect();
    /**
     * List of event IDs touching targets
     */
    private Set<Integer> mTouchingMinTarget = new HashSet<>();
    private Set<Integer> mTouchingMaxTarget = new HashSet<>();


    private OnRangeSliderListener mOnRangeSliderListener;

    //========属性==========//
    private int mVerticalPadding;
    private Color mInsideRangeColor;
    private Color mOutsideRangeColor;
    private float mInsideRangeLineStrokeWidth;
    private float mOutsideRangeLineStrokeWidth;
    private int mMin = 0;
    private int mMax = DEFAULT_MAX;
    private PixelMap mSliderIcon;
    private PixelMapHolder mSliderpixelMapHolder;
    private PixelMap mSliderIconFocus;
    private PixelMapHolder mSliderFocuspixelMapHolder;
    private boolean mIsLineRound;

    private boolean mIsShowBubble;
    private PixelMap mBubbleBitmap;
    private PixelMapHolder pixelMapHolder; //添加容器
//    private boolean mIsShowNumber;
    private Color mNumberTextColor;
    private int mNumberTextSize;
    private float mNumberMarginBottom;

    private boolean mIsShowRuler;
    private Color mRulerColor;
    private Color mRulerTextColor;
    private int mRulerTextSize;
    private int mRulerMarginTop;
    private int mRulerDividerHeight;
    private int mRulerTextMarginTop;
    private int mRulerInterval;

    public XRangeSlider(Context context) {
        this(context, null);
    }

    public XRangeSlider(Context context, AttrSet attrs) {
        this(context, attrs, 0);
    }

    public XRangeSlider(Context context, AttrSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(context, attrs, defStyleAttr);
        setEstimateSizeListener(this);
        addDrawTask(this);
        setTouchEventListener(this);
    }

    public void initAttrs(Context context, AttrSet attrs, int defStyleAttr) {
        DEFAULT_TOUCH_TARGET_SIZE = SizeUtils.fp2px(context, 20);
        DEFAULT_TEXT_MIN_SPACE = SizeUtils.fp2px(context, 2);
        Color colorAccent = AttrValue.get(attrs, "colorAccent", new Color(Color.getIntColor("#299EE3")));
        Color colorControlNormal = AttrValue.get(attrs, "colorControlNormal", new Color(Color.getIntColor("#BEC2C7")));
        if (attrs != null) {
            mVerticalPadding = AttrValue.getDimension(attrs, "xrs_verticalPadding", SizeUtils.fp2px(context, 10));
            //滑条
            mInsideRangeColor = AttrValue.get(attrs, "xrs_insideRangeLineColor", colorAccent);
            mOutsideRangeColor = AttrValue.get(attrs, "xrs_outsideRangeLineColor", new Color(Color.getIntColor("#F0F0F0")));
            mInsideRangeLineStrokeWidth = AttrValue.getDimension(attrs, "xrs_insideRangeLineStrokeWidth", SizeUtils.fp2px(context, 4));
            mOutsideRangeLineStrokeWidth = AttrValue.getDimension(attrs, "xrs_outsideRangeLineStrokeWidth", SizeUtils.fp2px(context, 4));
            mMin = AttrValue.get(attrs, "xrs_min", mMin);
            mMax = AttrValue.get(attrs, "xrs_max", mMax);
            mSliderIcon = getPixelMap(ResourceTable.Media_xui_ic_slider_icon);
            mSliderIconFocus = getPixelMap(ResourceTable.Media_xui_ic_slider_icon);
            mIsLineRound = AttrValue.get(attrs, "xrs_isLineRound", true);

            //气泡
            mIsShowBubble = AttrValue.get(attrs, "xrs_isShowBubble", false);
            boolean isFitColor = AttrValue.get(attrs, "xrs_isFitColor", true);

            //mIsShowNumber=AttrValue.get(attrs,"xrs_isShowNumber",true);

            mNumberTextColor = AttrValue.get(attrs, "xrs_numberTextColor", colorAccent);
            mNumberTextSize = AttrValue.getDimension(attrs, "xrs_numberTextSize", SizeUtils.fp2px(context, 12));
            mNumberMarginBottom = AttrValue.getDimension(attrs, "xrs_numberMarginBottom", SizeUtils.fp2px(context, 0));

            if (isFitColor) {
                if (mIsShowBubble) {
                    mNumberTextColor = Color.WHITE;
                }
                mBubbleBitmap = getPixelMap(ResourceTable.Media_xui_bg_bubble_gree);
            } else {
                mBubbleBitmap = getPixelMap(ResourceTable.Media_xui_bg_bubble_blue);
            }


            //刻度尺
            mIsShowRuler = AttrValue.get(attrs, "xrs_isShowRuler", false);
            mRulerColor = AttrValue.get(attrs, "xrs_rulerColor", colorControlNormal);
            mRulerTextColor = AttrValue.get(attrs, "xrs_rulerTextColor", colorControlNormal);
            mRulerTextSize = AttrValue.getDimension(attrs, "xrs_rulerTextSize", SizeUtils.fp2px(context, 12));
            mRulerMarginTop = AttrValue.getDimension(attrs, "xrs_rulerMarginTop", SizeUtils.fp2px(context, 4));
            mRulerDividerHeight = AttrValue.getDimension(attrs, "xrs_rulerDividerHeight", SizeUtils.fp2px(context, 4));
            mRulerTextMarginTop = AttrValue.getDimension(attrs, "xrs_rulerTextMarginTop", SizeUtils.fp2px(context, 4));
            mRulerInterval = AttrValue.get(attrs, "xrs_rulerInterval", 20);

        }
        mRange = mMax - mMin;
    }

    private PixelMap getPixelMap(int resId) {
        InputStream drawableInputStream = null;
        try {
            drawableInputStream = getResourceManager().getResource(resId);
            ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions();
            sourceOptions.formatHint = "image/png";
            ImageSource imageSource = ImageSource.create(drawableInputStream, sourceOptions);
            ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
            PixelMap pixelMap = imageSource.createPixelmap(decodingOptions);
            return pixelMap;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (drawableInputStream != null) {
                    drawableInputStream.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private void drawEntireRangeLine(Canvas canvas) {
        mPaint.setColor(mOutsideRangeColor);
        mPaint.setStrokeWidth(mOutsideRangeLineStrokeWidth);
        canvas.drawLine(mLineStartX, mMiddleY, mLineEndX, mMiddleY, mPaint);
        if (mIsLineRound) {
            canvas.drawCircle(mLineStartX, mMiddleY, mOutsideRangeLineStrokeWidth / 2, mPaint);
            canvas.drawCircle(mLineEndX, mMiddleY, mOutsideRangeLineStrokeWidth / 2, mPaint);
        }
    }

    private void drawSelectedRangeLine(Canvas canvas) {
        mPaint.setStrokeWidth(mInsideRangeLineStrokeWidth);
        mPaint.setColor(mInsideRangeColor);
        canvas.drawLine(mMinPosition, mMiddleY, mMaxPosition, mMiddleY, mPaint);
    }

    private void drawSelectedNumber(Canvas canvas) {
        String min = String.valueOf(getSelectedMin());
        String max = String.valueOf(getSelectedMax());
        mMinTextRect = getTextBounds(min);
        mMaxTextRect = getTextBounds(max);

        float minXBubble = mMinPosition - mBubbleBitmap.getImageInfo().size.width / 2F;
        float maxXBubble = mMaxPosition - mBubbleBitmap.getImageInfo().size.width / 2F;
        int maxTextLengthBubble = mBubbleBitmap.getImageInfo().size.width + 5;

        if (mIsTouching && mTouchingMaxTarget.size() > 0) {
            if (Math.abs(maxXBubble - minXBubble) <= maxTextLengthBubble) {
                maxXBubble = minXBubble + maxTextLengthBubble;

                if (maxXBubble > mLineEndX - maxTextLengthBubble / 2F) {
                    maxXBubble = mLineEndX - maxTextLengthBubble / 2F;
                }
            }
        }

        if (mIsTouching && mTouchingMinTarget.size() > 0) {
            if (Math.abs(maxXBubble - minXBubble) <= maxTextLengthBubble) {
                minXBubble = maxXBubble - maxTextLengthBubble;

                if (minXBubble < mLineStartX) {
                    minXBubble = mLineStartX;
                }
            }
        }

        if (Math.abs(maxXBubble - minXBubble) <= maxTextLengthBubble) {

            if (!mIsTouchingMin) {
                maxXBubble = minXBubble + maxTextLengthBubble;
                if (maxXBubble > mLineEndX - maxTextLengthBubble / 2F) {
                    maxXBubble = mLineEndX - maxTextLengthBubble / 2F;
                    minXBubble = maxXBubble - maxTextLengthBubble;
                }
            } else {
                minXBubble = maxXBubble - maxTextLengthBubble;
                if (minXBubble < mLineStartX + maxTextLengthBubble / 2F) {
                    minXBubble = mLineStartX + maxTextLengthBubble / 2F;
                    maxXBubble = minXBubble + maxTextLengthBubble;
                }
            }
        }


        float yText;
        //bubble
        if (mIsShowBubble) {

            float top = mMiddleY - mSliderIcon.getImageInfo().size.height / 2F - mBubbleBitmap.getImageInfo().size.height - mNumberMarginBottom;
            yText = top + mBubbleBitmap.getImageInfo().size.height / 2F + mMinTextRect.getHeight() / 2F - 6;
            pixelMapHolder = new PixelMapHolder(mBubbleBitmap);

            canvas.drawPixelMapHolder(pixelMapHolder, maxXBubble, top, mPaint);
            canvas.drawPixelMapHolder(pixelMapHolder, minXBubble, top, mPaint);

        } else {
            yText = mMiddleY - mSliderIcon.getImageInfo().size.height / 2F - mNumberMarginBottom;
        }

        //text

        float minX = mMinPosition - mMinTextRect.getWidth() / 2F;
        float maxX = mMaxPosition - mMaxTextRect.getWidth() / 2F - 6;
        int maxTextLength = mIsShowBubble ? mBubbleBitmap.getImageInfo().size.width : getMaxTextLength() + 5;

        if (mIsTouching && mTouchingMaxTarget.size() > 0) {
            if (Math.abs(maxX - minX) <= maxTextLength) {
                maxX = minX + maxTextLength;

                if (maxX > mLineEndX - maxTextLength / 2F) {
                    maxX = mLineEndX - maxTextLength / 2F;
                }
            }
        }

        if (mIsTouching && mTouchingMinTarget.size() > 0) {
            if (Math.abs(maxX - minX) <= maxTextLength) {
                minX = maxX - maxTextLength;

                if (minX < mLineStartX) {
                    minX = mLineStartX;
                }
            }
        }

        if (Math.abs(maxX - minX) <= maxTextLength) {
            if (!mIsTouchingMin) {
                maxX = minX + maxTextLength;
                if (maxX > mLineEndX - maxTextLength / 2F) {
                    maxX = mLineEndX - maxTextLength / 2F;
                    minX = maxX - maxTextLength;
                }
            } else {
                minX = maxX - maxTextLength;
                if (minX < mLineStartX + maxTextLength / 2F) {
                    minX = mLineStartX + maxTextLength / 2F;
                    maxX = minX + maxTextLength;
                }
            }
        }


        mPaint.setTextSize(mNumberTextSize);

        mPaint.setColor(mNumberTextColor);
        canvas.drawText(mPaint, min, minX, yText);

        mPaint.setColor(mNumberTextColor);
        canvas.drawText(mPaint, max, maxX, yText);

    }

    private void drawRuler(Canvas canvas) {
        if (mIsShowRuler) {
            float startX = mLineStartX;
            float stopY = 0;
            float startY = 0;
            float divider = (float) mRulerInterval / 10f;
            float scaleLength = (float) mLineLength / ((mMax - mMin) / divider) / divider;

            boolean isMinHasText = false;
            boolean isMaxHasText = false;

            for (int i = mMin; i <= mMax; i++) {
                if (i % mRulerInterval == 0) {
                    //draw big scale
                    startY = mMiddleY + mSliderIcon.getImageInfo().size.height / 2F + mRulerMarginTop;
                    stopY = startY + mRulerDividerHeight * 3;

                    mPaint.setColor(mRulerTextColor);
                    mPaint.setTextSize(mRulerTextSize);
                    mRulerTextRect = getRulerTextBounds(String.valueOf(i));
                    canvas.drawText(mPaint, String.valueOf(i), startX - mRulerTextRect.getWidth() / 2F, stopY + mRulerTextRect.getHeight() + mRulerTextMarginTop);
                    if (i == mMin) {
                        isMinHasText = true;
                    }
                    if (i == mMax) {
                        isMaxHasText = true;
                    }
                    mPaint.setStrokeWidth(DEFAULT_BIG_SCALE_WITH);

                    mPaint.setColor(mRulerColor);

                    canvas.drawLine(startX, startY, startX, stopY, mPaint);

                } else if (i % (mRulerInterval / 2) == 0) {
                    //draw middle scale
                    startY = mMiddleY + mSliderIcon.getImageInfo().size.height / 2F + mRulerMarginTop;
                    stopY = startY + mRulerDividerHeight * 2;
                    mPaint.setStrokeWidth(DEFAULT_MIDDLE_SCALE_WITH);

                    mPaint.setColor(mRulerColor);
                    canvas.drawLine(startX, startY, startX, stopY, mPaint);


                } else {
                    //draw small scale
                    startY = mMiddleY + mSliderIcon.getImageInfo().size.height / 2F + mRulerMarginTop;
                    stopY = startY + mRulerDividerHeight;
                    mPaint.setStrokeWidth(DEFAULT_SMALL_SCALE_WITH);

                    if (i % (mRulerInterval / 10) == 0) {
                        mPaint.setColor(mRulerColor);
                        canvas.drawLine(startX, startY, startX, stopY, mPaint);
                    }

                }

                if ((i == mMax && !isMaxHasText) || (i == mMin && !isMinHasText)) {

                    mPaint.setColor(mRulerTextColor);
                    mPaint.setTextSize(mRulerTextSize);
                    mRulerTextRect = getRulerTextBounds(String.valueOf(i));

                    float x = startX - mRulerTextRect.getWidth() / 2F;
                    //修正最大值与最小值文本与满刻度文本太靠近时显示重叠问题
                    if (i == mMax && i % mRulerInterval == 1) {
                        x = startX + DEFAULT_TEXT_MIN_SPACE;
                    }

                    if (i == mMin && i % mRulerInterval == mRulerInterval - 1) {
                        x = startX - mRulerTextRect.getWidth() / 2F - DEFAULT_TEXT_MIN_SPACE;
                    }

                    canvas.drawText(mPaint, String.valueOf(i), x, startY + mRulerDividerHeight * 3 + mRulerTextRect.getHeight() + mRulerTextMarginTop);

                }
                startX += scaleLength;
            }
        }
    }

    private void drawSelectedTargets(Canvas canvas) {
        mPaint.setColor(mInsideRangeColor);
        canvas.drawCircle(mMinPosition, mMiddleY, SizeUtils.fp2px(getContext(), 3), mPaint);
        canvas.drawCircle(mMaxPosition, mMiddleY, SizeUtils.fp2px(getContext(), 3), mPaint);
        if (mIsTouching) {
            if (mLastTouchedMin) {
                mSliderFocuspixelMapHolder = new PixelMapHolder(mSliderIconFocus);
                canvas.drawPixelMapHolder(mSliderFocuspixelMapHolder, mMinPosition - mSliderIcon.getImageInfo().size.width / 2F, mMiddleY - mSliderIconFocus.getImageInfo().size.width / 2F, mPaint);
                mSliderpixelMapHolder = new PixelMapHolder(mSliderIcon);
                canvas.drawPixelMapHolder(mSliderpixelMapHolder, mMaxPosition - mSliderIcon.getImageInfo().size.width / 2F, mMiddleY - mSliderIcon.getImageInfo().size.width / 2F, mPaint);
            } else {
                mSliderpixelMapHolder = new PixelMapHolder(mSliderIcon);
                canvas.drawPixelMapHolder(mSliderpixelMapHolder, mMinPosition - mSliderIcon.getImageInfo().size.width / 2F, mMiddleY - mSliderIcon.getImageInfo().size.width / 2F, mPaint);
                mSliderFocuspixelMapHolder = new PixelMapHolder(mSliderIconFocus);
                canvas.drawPixelMapHolder(mSliderFocuspixelMapHolder, mMaxPosition - mSliderIcon.getImageInfo().size.width / 2F, mMiddleY - mSliderIconFocus.getImageInfo().size.width / 2F, mPaint);
            }
        } else {
            mSliderpixelMapHolder = new PixelMapHolder(mSliderIcon);
            canvas.drawPixelMapHolder(mSliderpixelMapHolder, mMaxPosition - mSliderIcon.getImageInfo().size.width / 2F, mMiddleY - mSliderIcon.getImageInfo().size.width / 2F, mPaint);
            mSliderpixelMapHolder = new PixelMapHolder(mSliderIcon);
            canvas.drawPixelMapHolder(mSliderpixelMapHolder, mMinPosition - mSliderIcon.getImageInfo().size.width / 2F, mMiddleY - mSliderIcon.getImageInfo().size.width / 2F, mPaint);
        }
    }

    private Rect getTextBounds(String text) {
        mPaint.setTextSize(mNumberTextSize);
        return mPaint.getTextBounds(text);
    }

    private Rect getRulerTextBounds(String text) {
        mPaint.setTextSize(mRulerTextSize);
        return mPaint.getTextBounds(text);
    }

    private void jumpToPosition(int index, TouchEvent event) {
        //user has touched outside the target, lets jump to that position
        if (event.getPointerScreenPosition(index).getX() > mMaxPosition && event.getPointerScreenPosition(index).getX() <= mLineEndX) {
            mMaxPosition = (int) event.getPointerScreenPosition(index).getX();
            invalidate();
            callMaxChangedCallbacks();
        } else if (event.getPointerScreenPosition(index).getX() < mMinPosition && event.getPointerScreenPosition(index).getX() >= mLineStartX) {
            mMaxPosition = (int) event.getPointerScreenPosition(index).getX();
            invalidate();
            callMinChangedCallbacks();
        }
    }

    private boolean checkTouchingMinTarget(int index, TouchEvent event) {
        if (isTouchingMinTarget(index, event)) {
            mLastTouchedMin = true;
            mTouchingMinTarget.add(event.getPointerId(index));
            return true;
        }
        return false;
    }

    private boolean checkTouchingMaxTarget(int index, TouchEvent event) {
        if (isTouchingMaxTarget(index, event)) {
            mLastTouchedMin = false;
            mTouchingMaxTarget.add(event.getPointerId(index));
            return true;
        }
        return false;
    }

    private void callMinChangedCallbacks() {
        if (mOnRangeSliderListener != null) {
            mOnRangeSliderListener.onMinChanged(this, getSelectedMin());
        }
    }

    private void callMaxChangedCallbacks() {
        if (mOnRangeSliderListener != null) {
            mOnRangeSliderListener.onMaxChanged(this, getSelectedMax());
        }
    }

    private boolean isTouchingMinTarget(int pointerIndex, TouchEvent event) {
        return event.getPointerPosition(pointerIndex).getX() > mMinPosition - DEFAULT_TOUCH_TARGET_SIZE
            && event.getPointerPosition(pointerIndex).getX() < mMinPosition + DEFAULT_TOUCH_TARGET_SIZE
            && event.getPointerPosition(pointerIndex).getY() > mMiddleY - DEFAULT_TOUCH_TARGET_SIZE
            && event.getPointerPosition(pointerIndex).getY() < mMiddleY + DEFAULT_TOUCH_TARGET_SIZE;
    }

    private boolean isTouchingMaxTarget(int pointerIndex, TouchEvent event) {
        return event.getPointerPosition(pointerIndex).getX() > mMaxPosition - DEFAULT_TOUCH_TARGET_SIZE
            && event.getPointerPosition(pointerIndex).getX() < mMaxPosition + DEFAULT_TOUCH_TARGET_SIZE
            && event.getPointerPosition(pointerIndex).getY() > mMiddleY - DEFAULT_TOUCH_TARGET_SIZE
            && event.getPointerPosition(pointerIndex).getY() < mMiddleY + DEFAULT_TOUCH_TARGET_SIZE;
    }

    private void calculateConvertFactor() {
        mConvertFactor = ((float) mRange) / mLineLength;
    }


    public int getSelectedMin() {
        return Math.round((mMinPosition - mLineStartX) * mConvertFactor + mMin);
    }

    public int getSelectedMax() {
        return Math.round((mMaxPosition - mLineStartX) * mConvertFactor + mMin);
    }

    private int getMaxTextLength() {
        return getTextBounds(String.valueOf(mMax)).getWidth();
    }

    private void setSelectedValue(int selectedMax) {
        mMaxPosition = Math.round(((selectedMax - mMin) / mConvertFactor) + mLineStartX);
        callMaxChangedCallbacks();
    }


    public int getMin() {
        return mMin;
    }

    public void setMin(int min) {
        mMin = min;
        mRange = mMax - min;
    }

    public int getMax() {
        return mMax;
    }

    public void setMax(int max) {
        mMax = max;
        mRange = max - mMin;
    }

    public void setInterval(int rulerInterval) {
        mRulerInterval = rulerInterval;
        invalidate();
    }

    /**
     * 设置滑动器最小值和最大值
     *
     * @param startingMin
     * @param startingMax
     */
    public void setStartingMinMax(int startingMin, int startingMax) {
        mStartingMin = startingMin;
        mStartingMax = startingMax;
        setSelectedMin(startingMin);
        setSelectedMax(startingMax);
        invalidate();
    }

    private void setSelectedMin(int selectedMin) {
        mMinPosition = Math.round(((selectedMin - mMin) / mConvertFactor) + mLineStartX);
        callMinChangedCallbacks();
    }

    private void setSelectedMax(int selectedMax) {
        mMaxPosition = Math.round(((selectedMax - mMin) / mConvertFactor) + mLineStartX);
        callMaxChangedCallbacks();
    }

    public void setOnRangeSliderListener(OnRangeSliderListener listener) {
        mOnRangeSliderListener = listener;
    }

    /**
     * Resets selected values to MIN and MAX.
     */
    public void reset() {
        mMinPosition = mLineStartX;
        mMaxPosition = mLineEndX;
        if (mOnRangeSliderListener != null) {
            mOnRangeSliderListener.onMinChanged(this, getSelectedMin());
            mOnRangeSliderListener.onMaxChanged(this, getSelectedMax());
        }
        invalidate();
    }

    /**
     * clamp
     *
     * @param value 1
     * @param min 2
     * @param max 3
     * @param <T> 4
     * @return 5
     */
    private <T extends Number> T clamp(T value, T min, T max) {
        if (value.doubleValue() > max.doubleValue()) {
            return max;
        } else if (value.doubleValue() < min.doubleValue()) {
            return min;
        }
        return value;
    }

    private int width;
    private int height;

    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = EstimateSpec.getMode(widthMeasureSpec);
        int widthSize = EstimateSpec.getSize(widthMeasureSpec);
        int heightMode = EstimateSpec.getMode(heightMeasureSpec);
        int heightSize = EstimateSpec.getSize(heightMeasureSpec);
        int desiredWidth = widthSize;
        int desiredHeight;
        mMinTextRect = getTextBounds(String.valueOf(mMin));
        mMaxTextRect = getTextBounds(String.valueOf(mMax));
        if (mIsShowBubble) {
            desiredHeight = (int) (mSliderIcon.getImageInfo().size.height + mNumberMarginBottom) + mBubbleBitmap.getImageInfo().size.height;
        } else {
            desiredHeight = (int) (mSliderIcon.getImageInfo().size.height + mNumberMarginBottom) + mMinTextRect.getHeight();
        }
        int rulerHeight = (int) (mRulerMarginTop + mRulerDividerHeight * 3 + mRulerTextMarginTop + mRulerTextRect.getHeight());
        if (mIsShowRuler) {
            mRulerTextRect = getRulerTextBounds(String.valueOf(mMin));
            desiredHeight += rulerHeight;
        }

        width = widthSize;
        height = desiredHeight;
        if (widthMode == EstimateSpec.NOT_EXCEED) {
            width = widthSize;
        } else if (widthMode == EstimateSpec.NOT_EXCEED) {
            width = Math.min(desiredWidth, widthSize);
        } else {
            width = desiredWidth;
        }
        height = desiredHeight;
        if (heightMode == EstimateSpec.NOT_EXCEED) {
            height = heightSize;
        } else if (heightMode == EstimateSpec.NOT_EXCEED) {
            height = desiredHeight;
        }

        height += mVerticalPadding;

        int marginStartEnd = mIsShowBubble ? mBubbleBitmap.getImageInfo().size.width : Math.max(mSliderIcon.getImageInfo().size.width, mMaxTextRect.getWidth());

        mLineLength = (width - marginStartEnd);

        mMiddleY = mIsShowRuler ? height - rulerHeight - mSliderIcon.getImageInfo().size.height / 2 :
            height - mSliderIcon.getImageInfo().size.height / 2;
        mLineStartX = marginStartEnd / 2;
        mLineEndX = mLineLength + marginStartEnd / 2;

        calculateConvertFactor();

        if (mIsFirstInit) {
            setSelectedMin(mStartingMin != -1 ? mStartingMin : mMin);
            setSelectedMax(mStartingMax != -1 ? mStartingMax : mMax);
        }
        height += mVerticalPadding;
        setEstimatedSize(width, height);
        return false;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        drawEntireRangeLine(canvas);
        drawSelectedRangeLine(canvas);
        drawSelectedNumber(canvas);
        drawRuler(canvas);
        drawSelectedTargets(canvas);

    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        if (!isEnabled()) {
            return false;
        }
        mIsFirstInit = false;
        final int actionIndex = touchEvent.getIndex();
        switch (touchEvent.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                // updateTouchStatus(true);
                if (mLastTouchedMin) {
                    if (!checkTouchingMinTarget(actionIndex, touchEvent)
                        && !checkTouchingMaxTarget(actionIndex, touchEvent)) {
                        jumpToPosition(actionIndex, touchEvent);
                    }
                } else if (!checkTouchingMaxTarget(actionIndex, touchEvent)
                    && !checkTouchingMinTarget(actionIndex, touchEvent)) {
                    jumpToPosition(actionIndex, touchEvent);
                }
                break;
            case TouchEvent.OTHER_POINT_UP:
            case TouchEvent.PRIMARY_POINT_UP:
                mTouchingMinTarget.remove(touchEvent.getPointerId(actionIndex));
                mTouchingMaxTarget.remove(touchEvent.getPointerId(actionIndex));

                invalidate();
                break;

            case TouchEvent.POINT_MOVE:
                // updateTouchStatus(true);
                // updateTouchStatus(false);
                for (int i = 0; i < touchEvent.getPointerCount(); i++) {
                    if (mTouchingMinTarget.contains(touchEvent.getPointerId(i))) {
                        int touchX = (int) touchEvent.getPointerPosition(i).getX();
                        touchX = clamp(touchX, mLineStartX, mLineEndX);
                        if (touchX >= mMaxPosition) {
                            mMaxPosition = touchX;
                            callMaxChangedCallbacks();
                        }
                        mMinPosition = touchX;
                        callMinChangedCallbacks();
                        mIsTouchingMin = true;
                    }
                    if (mTouchingMaxTarget.contains(touchEvent.getPointerId(i))) {
                        int touchX = (int) touchEvent.getPointerPosition(i).getX();
                        touchX = clamp(touchX, mLineStartX, mLineEndX);
                        if (touchX <= mMinPosition) {
                            mMinPosition = touchX;
                            callMinChangedCallbacks();
                        }
                        mMaxPosition = touchX;
                        callMaxChangedCallbacks();
                        mIsTouchingMin = false;
                    }
                }
                invalidate();
                break;

            case TouchEvent.OTHER_POINT_DOWN:
                // updateTouchStatus(false);
                for (int i = 0; i < touchEvent.getPointerCount(); i++) {
                    if (mLastTouchedMin) {
                        if (!checkTouchingMinTarget(i, touchEvent)
                            && !checkTouchingMaxTarget(i, touchEvent)) {
                            jumpToPosition(i, touchEvent);
                        }
                    } else if (!checkTouchingMaxTarget(i, touchEvent)
                        && !checkTouchingMinTarget(i, touchEvent)) {
                        jumpToPosition(i, touchEvent);
                    }
                }

                break;

            case TouchEvent.CANCEL:
                // updateTouchStatus(false);
                mTouchingMinTarget.clear();
                mTouchingMaxTarget.clear();
                invalidate();
                break;

            default:
                break;
        }

        return true;
    }
}